#include "advancingFront3D.h"
#include "advancingFront2D.h"
#include "tetrahedron.h"

AdvancingFront3D::AdvancingFront3D() : Methods(){
}

AdvancingFront3D::~AdvancingFront3D(){
}

void AdvancingFront3D::calculate(Object *_obj){
    if(_obj->sizeVertices() < 5) return;

    std::cout <<"----------------------------------------- Avanço de fronteira"<<std::endl;

    std::vector<Face *> facesList, triangulatedFaces;              //Lista de faces, F, a serem visitadas
    Face *face;

    //inserindo as faces na lista
    //tendo o cuidado de tringularizar as faces antes de inserir
    int size = _obj->sizeFaces();
    for(int i = size; i > 0; i--){
        face = _obj->getFace(i - 1);
        if(face->getVerticesInThePlanSize() < 3){
            std::cout <<"FACE SO COM DOIS VERTICES! ERRO!" <<std::endl;
        }else if(face->getVerticesInThePlanSize() == 3){
            std::cout <<"Adicionando a face " << face->getID() << std::endl;
            facesList.push_back(face);
        }else{
            std::cout <<"Indo ver a face " << face->getID() << std::endl;
            AdvancingFront2D advancingFront;
            triangulatedFaces = advancingFront.calculate(_obj, face);
            facesList.insert(facesList.end(), triangulatedFaces.begin(), triangulatedFaces.end());
        }
    }
//    std::cout <<"saiu! " <<std::endl;

    //depois de restruturar o convexhull para subdividir as faces, o novo conjunto de faces do objeto vai mudar para essa lista
    _obj->setFaces(facesList);

    _obj->print();

    tetrahedralizate(_obj);
}

void AdvancingFront3D::tetrahedralizate(Object *_obj){
    std::vector<Face *> faces = _obj->getFaces();
    std::vector<bool> facesSignals(faces.size(), true);
    std::vector<Face *> constraints = _obj->getFaces();
    Face *face;
    bool faceSignal;

    std::vector<Vertex *> vertices;
    bool notOK, edgeSignal, nextSignal, constraintEdgeSignal, constraintNextSignal;
    Edge *edge, *next, *constraintEdge, *constraintNext;
    int size, chosenVertexI;
    double currentSolidAngle, maxSolidAngle;
    Vertex *a, *b, *c, *constraintA, *constraintB, *constraintC, *vertex, *currentVertex;
    Face *constraint;

    Edge *e1, *e2, *e3;
    bool e1Signal, e2Signal, e3Signal;
    Face *f1, *f2, *f3;
    bool f1Signal, f2Signal, f3Signal;
    Tetrahedron *tetrahedron;

    while(!faces.empty()){
        face = faces.front();
        faceSignal = facesSignals.front();

        faces.erase(faces.begin());
        facesSignals.erase(facesSignals.begin());
//        std::cout <<"\n\n\nPegando a face "<<face->getID() << " para tetraedralizar." <<std::endl;

//        if(face->getID() == 15) break;
//
//        int nhe;
//        std::cin >> nhe;

        if(face->isBoundary()){
            edge = face->getEdge();
            edgeSignal = face->getEdgeSignal();

            //fazendo com que os pontos sejam no sentido da face
            if(edgeSignal){
                a = edge->getStart();

                next = edge->getNextClockwise();
                nextSignal = edge->getNextClockwiseSignal();
            }else{
                a = edge->getEnd();

                next = edge->getNextCounterClockwise();
                nextSignal = edge->getNextCounterClockwiseSignal();
            }
            if(nextSignal){
                b = next->getStart();
                c = next->getEnd();
            }else{
                b = next->getEnd();
                c = next->getStart();
            }
        }else{//peg ao contrario s enum for da borda
            a = face->getVertexInThePlan(0);
            b = face->getVertexInThePlan(1);
            c = face->getVertexInThePlan(2);
        }

        vertices = _obj->getVertices();
        notOK = true;
        vertex = NULL;

        while(notOK){
            size = vertices.size();
            //escolher o ponto a ser utilizado
            maxSolidAngle = -1;
            chosenVertexI = -1;
            for(int i = 0; i < size; i++){
                currentVertex = vertices.at(i);
                //dispensando verificar os pontos da face
                if(currentVertex == a || currentVertex == b || currentVertex == c) continue;

                //tambem poderia testar os pontos coplanares e colineares
                if(isCoplanar(a->getPoint(), b->getPoint(), c->getPoint(), currentVertex->getPoint())) continue;

                //tirando os pontos que estão atras da face
                //se a normal da face tiver apontando pra ele, descarta-se
                if(face->isBoundary()){
                    if(cosAlpha(face->normal(), currentVertex->getPoint() - a->getPoint()) >= 0.0) continue;
                }else{
                    if(cosAlpha(face->normal()*(-1.0), currentVertex->getPoint() - a->getPoint()) >= 0.0) continue;
                }

                //calculando o angulo solido
                currentSolidAngle = solidAngle(a->getPoint(), b->getPoint(), c->getPoint(), currentVertex->getPoint());
                if(currentSolidAngle > maxSolidAngle){
                    maxSolidAngle = currentSolidAngle;
                    chosenVertexI = i;
                }
            }
            //***APAGR ISSO!
            if(chosenVertexI == -1){
                std::cout <<"Deu problema! não achou I!" <<std::endl;
                vertex = NULL;
                break;
            }

            //testar contra as restições
            vertex = vertices.at(chosenVertexI);

//            std::cout <<"Foi encontrado o vertice "<<vertex->getID() <<"."<<std::endl;

            size = constraints.size();
            notOK = false;
//            std::cout <<"--Testando contra as " << size <<" constraints "<<std::endl;
            for(int i = 0; i < size; i++){
                constraint = constraints.at(i);

//                std::cout <<"\tconstraint " << i << ": "<<constraint->getID() <<"."<<std::endl;

                if(constraint->isBoundary()){
                    constraintEdge = constraint->getEdge();
                    constraintEdgeSignal = constraint->getEdgeSignal();
                    //precisa pegar os 3 pontos no loop correto
                    if(constraintEdgeSignal){
                        constraintA = constraintEdge->getStart();

                        constraintNext = constraintEdge->getNextClockwise();
                        constraintNextSignal = constraintEdge->getNextClockwiseSignal();
                    }else{
                        constraintA = constraintEdge->getEnd();

                        constraintNext = constraintEdge->getNextCounterClockwise();
                        constraintNextSignal = constraintEdge->getNextCounterClockwiseSignal();
                    }
                    if(constraintNextSignal){
                        constraintB = constraintNext->getStart();
                        constraintC = constraintNext->getEnd();
                    }else{
                        constraintB = constraintNext->getEnd();
                        constraintC = constraintNext->getStart();
                    }
                }else{
                    constraintA = constraint->getVertexInThePlan(0);
                    constraintB = constraint->getVertexInThePlan(1);
                    constraintC = constraint->getVertexInThePlan(2);
                }

                //testando quanto a primeira face
//                std::cout <<"1 teste: " <<std::endl;
                if(haveIntersection(constraintA->getPoint(), constraintB->getPoint(), constraintC->getPoint(), a->getPoint(), vertex->getPoint(), b->getPoint())){
//                    std::cout <<"Interseção com a primeira face!" <<std::endl;
//                    std::cout <<"Constraint: " <<std::endl;
//                    constraintA->getPoint().print();
//                    constraintB->getPoint().print();
//                    constraintC->getPoint().print();
//                    std::cout <<"Face criada: " <<std::endl;
//                    a->getPoint().print();
//                    vertex->getPoint().print();
//                    b->getPoint().print();

                    vertices.erase(vertices.begin() + chosenVertexI);
                    notOK = true;
                    break;
                }
                //testando quanto a segunda face
//                std::cout <<"2 teste: " <<std::endl;
                if(haveIntersection(constraintA->getPoint(), constraintB->getPoint(), constraintC->getPoint(), b->getPoint(), vertex->getPoint(), c->getPoint())){
//                    std::cout <<"Interseção com a segunda face!" <<std::endl;
//                    std::cout <<"Constraint: " <<std::endl;
//                    constraintA->getPoint().print();
//                    constraintB->getPoint().print();
//                    constraintC->getPoint().print();
//                    std::cout <<"Face criada: " <<std::endl;
//                    b->getPoint().print();
//                    vertex->getPoint().print();
//                    c->getPoint().print();

                    vertices.erase(vertices.begin() + chosenVertexI);
                    notOK = true;
                    break;
                }
                //testando quanto a terceira face
//                std::cout <<"3 teste: " <<std::endl;
                if(haveIntersection(constraintA->getPoint(), constraintB->getPoint(), constraintC->getPoint(), c->getPoint(), vertex->getPoint(), a->getPoint())){
//                    std::cout <<"Interseção com a terceira face!" <<std::endl;
//                    std::cout <<"Constraint: " <<std::endl;
//                    constraintA->getPoint().print();
//                    constraintB->getPoint().print();
//                    constraintC->getPoint().print();
//                    std::cout <<"Face criada: " <<std::endl;
//                    c->getPoint().print();
//                    vertex->getPoint().print();
//                    a->getPoint().print();

                    vertices.erase(vertices.begin() + chosenVertexI);
                    notOK = true;
                    break;
                }

            }
            //***APAGR ISSO!
            if(vertices.empty()){
                std::cout <<"Deu problema, nao achou nenhum!" <<std::endl;
            }
        }

        if(!vertex){
            std::cout <<"Vertex nulo!" <<std::endl;
            continue;
        }


//        std::cout <<"CRIANDO TETRAEDRO!" <<std::endl;
        //criando o tetraedro
        //criar as arestas
        e1 = new Edge(edgeID++, a, vertex);
        e1Signal = _obj->addEdge(&e1);
        if(e1->getID() != edgeID - 1){
//                std::cout <<"!!achou uma aresta que ja existia " << e1->getID() <<"foi de A para o Vertice" <<std::endl;
            edgeID = edgeID - 1;
        }

        e2 = new Edge(edgeID++, b, vertex);
        e2Signal = _obj->addEdge(&e2);
        if(e2->getID() != edgeID - 1){
//                std::cout <<"!!achou uma aresta que ja existia " << e2->getID() <<"foi de B para o Vertice" <<std::endl;
            edgeID = edgeID - 1;
        }

        e3 = new Edge(edgeID++, c, vertex);
        e3Signal = _obj->addEdge(&e3);
        if(e3->getID() != edgeID - 1){
//                std::cout <<"!!achou uma aresta que ja existia " << e3->getID() <<"foi de C para o Vertice" <<std::endl;
            edgeID = edgeID - 1;
        }


        //criar faces
        f1 = new Face(faceID++, _obj);
        f1->addVertexInThePlan(a);
        f1->addVertexInThePlan(vertex);
        f1->addVertexInThePlan(b);
        f1Signal = _obj->addFace(&f1);
        if(f1->getID() != faceID - 1){
//            std::cout <<"!!achou uma face que ja existia " << f1->getID() <<std::endl;
            faceID = faceID - 1;

            for(unsigned int i = 0; i < faces.size(); i++){
                if(faces.at(i) == f1){
                    faces.erase(faces.begin() + i);
                    break;
                }
            }
        }else{
            faces.push_back(f1);
            constraints.push_back(f1);
        }

        f2 = new Face(faceID++, _obj);
        f2->addVertexInThePlan(b);
        f2->addVertexInThePlan(vertex);
        f2->addVertexInThePlan(c);
        f2Signal = _obj->addFace(&f2);
        if(f2->getID() != faceID - 1){
//                std::cout <<"!!achou uma face que ja existia " << f2->getID() <<std::endl;
            faceID = faceID - 1;

            for(unsigned int i = 0; i < faces.size(); i++){
                if(faces.at(i) == f2){
                    faces.erase(faces.begin() + i);
                    break;
                }
            }
        }else{
            faces.push_back(f2);
            constraints.push_back(f2);
        }

        f3 = new Face(faceID++, _obj);
        f3->addVertexInThePlan(c);
        f3->addVertexInThePlan(vertex);
        f3->addVertexInThePlan(a);
        f3Signal = _obj->addFace(&f3);
        if(f3->getID() != faceID - 1){
//                std::cout <<"!!achou uma face que ja existia " << f3->getID() <<std::endl;
            faceID = faceID - 1;
            //caso for achada pela segunda vez, tremove-se da lista de faces

            for(unsigned int i = 0; i < faces.size(); i++){
                if(faces.at(i) == f3){
                    faces.erase(faces.begin() + i);
                    break;
                }
            }
        }else{
            faces.push_back(f3);
            constraints.push_back(f3);
        }

        //adicionar o novo thetraedo em objeto, e ele terá o conhecimento das aus quatro faces
        tetrahedron = new Tetrahedron(tetrahedronID++);
        _obj->addTetrahedron(tetrahedron);

        tetrahedron->addVertex(a);
        tetrahedron->addVertex(b);
        tetrahedron->addVertex(c);
        tetrahedron->addVertex(vertex);
//
//        tetrahedron->addEdge(e1);
//        tetrahedron->addEdge(e2);
//        tetrahedron->addEdge(e3);

        tetrahedron->addFace(f1, f1Signal);
        tetrahedron->addFace(f2, f2Signal);
        tetrahedron->addFace(f3, f3Signal);
        tetrahedron->addFace(face, faceSignal);

    }

}
